package rlyy.framework.netty.inithandler.strategy;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.Feature;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
import rlyy.framework.core.common.enums.APIKey_Enum;
import rlyy.framework.core.common.enums.CommonEnum;
import rlyy.framework.core.common.response.ResponseResult;
import rlyy.framework.core.common.response.ResultUtil;
import rlyy.framework.netty.inithandler.HttpServerHandler;
import rlyy.framework.netty.support.DefaultParameterConversion;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.*;

/**
 * Netty转换Servlet请求处理器
 *
 * @author yuanqinglong
 * @since 2020/11/11 9:34
 */
@Component
public class ServletRequestProcess implements IRequestProcess {

    static final Logger logger = LoggerFactory.getLogger(ServletRequestProcess.class);

    @Autowired
    private Servlet dispatcherServlet;

    /**
     * 调用方配置的请求方式、不配置Servlet默认处理GET请求
     */
    @Autowired
    private RequestMethodConfiguration requestMethodConfiguration;

    /**
     * 根据配置判断是否是Servlet类处理的请求
     *
     * @param request 请求信息 判断此请求的请求方式(POST、GET、PUT、DELETE。。。)
     * @return true OR false
     */
    @Override
    public boolean supportsHandler(FullHttpRequest request) {
        return requestMethodConfiguration.getServletMethods().contains(request.method().name());
    }


    @Override
    public void requestHandler(ChannelHandlerContext ctx, FullHttpRequest request) {
        this.doServletRequest(ctx, request);
    }


    /**
     * 转换servlet请求逻辑
     *
     * @param ctx     .
     * @param request 请求信息
     */
    public void doServletRequest(ChannelHandlerContext ctx, FullHttpRequest request) {

        String param = request.content().toString(CharsetUtil.UTF_8);
        String remoteIp = this.getRemoteIp(request, ctx);
        logger.info("IP             : {}", remoteIp);
        logger.info("request        : {}", request.uri());
        logger.debug("param          : {}", param);
        // 将http编解码器传过来的FullHttpRequest，转换为 MockHttpServletRequest
        MockHttpServletRequest servletRequest = createServletRequest(request);
        servletRequest.setAttribute("box", DefaultParameterConversion.conversion(request));
        RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(servletRequest));
        MockHttpServletResponse servletResponse = new MockHttpServletResponse();
        HttpResponseStatus status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
        FullHttpResponse response = null;
        String contentType = "text/plain; charset=UTF-8";
        String tid = Optional.ofNullable(servletRequest.getParameter(APIKey_Enum.TID.getKey())).orElse("");
        MDC.put("tid", tid);
        try {
            // 调用SpringMVC  DispatcherServlet执行SpringMVC处理流程
            this.dispatcherServlet.service(servletRequest, servletResponse);
            if (Strings.isNotBlank(servletResponse.getForwardedUrl())) {
                request.setUri(servletResponse.getForwardedUrl());
                doServletRequest(ctx, request);
                return;
            }
            status = HttpResponseStatus.valueOf(servletResponse.getStatus());
            if (Strings.isNotBlank(servletResponse.getContentType())) {
                contentType = servletResponse.getContentType();
            }
            byte[] contentAsByteArray = servletResponse.getContentAsByteArray();
            if (Objects.equals(status, HttpResponseStatus.NOT_FOUND)) {
                ResponseResult error = ResultUtil.error(CommonEnum.API_NOT_FIND);
                contentAsByteArray = JSONObject.toJSONBytes(error);
            }
            // 转换返回response
            response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status,
                    Unpooled.copiedBuffer(contentAsByteArray));
            for (String name : servletResponse.getHeaderNames()) {
                for (Object value : servletResponse.getHeaderValues(name)) {
                    response.headers().add(name, value);
                }
            }
        } catch (ServletException | IOException e) {
            response = sendError(status);
            logger.error("error detail:{} param:{}", e, JSONObject.toJSONString(param));
        } finally {
            response = (response != null ? response : sendError(status));
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType);
            HttpServerHandler.writeResponse(ctx, request, response);
        }
    }

    /**
     * FullHttpRequest 转换 HttpServletRequest
     *
     * @param fullHttpRequest netty请求
     * @return HttpServletRequest
     */
    public static MockHttpServletRequest createServletRequest(FullHttpRequest fullHttpRequest) {
        UriComponents uriComponents = UriComponentsBuilder.fromUriString(fullHttpRequest.uri()).build();
        MockHttpServletRequest servletRequest = new MockHttpServletRequest();
        servletRequest.setPathInfo(uriComponents.getPath());
        servletRequest.setMethod(fullHttpRequest.method().name());
        for (String name : fullHttpRequest.headers().names()) {
            servletRequest.addHeader(name, fullHttpRequest.headers().get(name));
        }
        ByteBuf nettyContent = fullHttpRequest.content();
        String content = nettyContent.toString(CharsetUtil.UTF_8);
        servletRequest.setContent(content.getBytes(CharsetUtil.UTF_8));
        Map<String, String> paramMap = JSONObject.parseObject(content, new TypeReference<LinkedHashMap<String, String>>() {
        }, Feature.OrderedField);
        servletRequest.setRequestURI(uriComponents.getPath());
        if (!CollectionUtils.isEmpty(paramMap)) {
            paramMap.forEach(servletRequest::addParameter);
        }
        if (uriComponents.getQuery() != null) {
            String query = UriUtils.decode(uriComponents.getQuery(), "UTF-8");
            servletRequest.setQueryString(query);
        }
        for (Map.Entry<String, List<String>> entry : uriComponents.getQueryParams().entrySet()) {
            for (String value : entry.getValue()) {
                servletRequest.addParameter(
                        UriUtils.decode(entry.getKey(), "UTF-8"),
                        UriUtils.decode(value, "UTF-8"));
            }
        }
        if (uriComponents.getScheme() != null) {
            servletRequest.setScheme(uriComponents.getScheme());
        }
        if (uriComponents.getHost() != null) {
            servletRequest.setServerName(uriComponents.getHost());
        }
        if (uriComponents.getPort() != -1) {
            servletRequest.setServerPort(uriComponents.getPort());
        }
        return servletRequest;
    }


    private static FullHttpResponse sendError(HttpResponseStatus status) {
        ResponseResult result = ResultUtil.error(CommonEnum.SERVER_IS_BUSY);
        return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status,
                Unpooled.copiedBuffer(JSONObject.toJSONBytes(result)));
    }


    public String getRemoteIp(FullHttpRequest httpRequest, ChannelHandlerContext channelHandlerContext) {
        Channel channel = channelHandlerContext.channel();
        String ip = "";
        try {
            String ipForwarded = httpRequest.headers().get("x-forwarded-for");
            if (StringUtils.isBlank(ipForwarded) || "unknown".equalsIgnoreCase(ipForwarded)) {
                InetSocketAddress insocket = (InetSocketAddress) channel.remoteAddress();
                ip = insocket.getAddress().getHostAddress();
            } else {
                ip = ipForwarded;
            }
        } catch (Exception e) {
            logger.error("getRemoteIP(): get remote ip fail!", e);
        }
        if (Objects.equals("0:0:0:0:0:0:0:1", ip)) {
            ip = "127.0.0.1";
        }
        return ip;
    }
}
